package com.rose.springbootgeneratefile.internal.db;

import com.rose.springbootgeneratefile.api.*;
import com.rose.springbootgeneratefile.api.dom.java.FullyQualifiedJavaType;
import com.rose.springbootgeneratefile.config.Context;
import com.rose.springbootgeneratefile.config.TableConfiguration;
import com.rose.springbootgeneratefile.internal.ObjectFactory;
import com.rose.springbootgeneratefile.utils.StringUtility;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.rose.springbootgeneratefile.internal.utils.JavaBeansUtil.getCamelCaseString;
import static com.rose.springbootgeneratefile.internal.utils.JavaBeansUtil.getValidPropertyName;

public class DatabaseIntrospector {


    /** The database meta data. */
    private DatabaseMetaData databaseMetaData;
    /**
     * 字段类型解析器
     */
    private JavaTypeResolver javaTypeResolver;

    /** The context. */
    private Context context;

    public DatabaseIntrospector(Context context,DatabaseMetaData databaseMetaData,JavaTypeResolver javaTypeResolver) {
        this.databaseMetaData = databaseMetaData;
        this.context = context;
        this.javaTypeResolver = javaTypeResolver;
    }

    public List<IntrospectedTable> introspectTables(TableConfiguration tc)
            throws SQLException {

        // get the raw columns from the DB
        Map<ActualTableName, List<IntrospectedColumn>> columns = getColumns(tc);

        if (columns.isEmpty()) {

            return null;
        }

//        removeIgnoredColumns(tc, columns);
        //计算额外的列信息
        calculateExtraColumnInformation(tc, columns);
//        applyColumnOverrides(tc, columns);
//        calculateIdentityColumns(tc, columns);

        List<IntrospectedTable> introspectedTables = calculateIntrospectedTables(
                tc, columns);

        // now introspectedTables has all the columns from all the
        // tables in the configuration. Do some validation...

//        Iterator<IntrospectedTable> iter = introspectedTables.iterator();
//        while (iter.hasNext()) {
//            IntrospectedTable introspectedTable = iter.next();
//
//            if (!introspectedTable.hasAnyColumns()) {
//                // add warning that the table has no columns, remove from the
//                // list
//
//                iter.remove();
//            } else if (!introspectedTable.hasPrimaryKeyColumns()
//                    && !introspectedTable.hasBaseColumns()) {
//
//                iter.remove();
//            } else {
//                // now make sure that all columns called out in the
//                // configuration
//                // actually exist
//                reportIntrospectionWarnings(introspectedTable, tc,
//                        introspectedTable.getFullyQualifiedTable());
//            }
//        }

        return introspectedTables;
    }

    /**
     * 获取全部列对象
     * @param tc
     * @return
     * @throws SQLException
     */
    private Map<ActualTableName, List<IntrospectedColumn>> getColumns(
            TableConfiguration tc) throws SQLException {
        String localCatalog;
        String localSchema;
        String localTableName;

        localCatalog = tc.getCatalog() == null ? null : tc.getCatalog()
                .toLowerCase();
        localSchema = tc.getSchema() == null ? null : tc.getSchema()
                .toLowerCase();
        localTableName = tc.getTableName() == null ? null : tc
                .getTableName().toLowerCase();



        Map<ActualTableName, List<IntrospectedColumn>> answer = new HashMap<ActualTableName, List<IntrospectedColumn>>();



        ResultSet rs = databaseMetaData.getColumns(localCatalog, localSchema,
                localTableName, "%"); //$NON-NLS-1$

        boolean supportsIsAutoIncrement = false;
        boolean supportsIsGeneratedColumn = false;
        ResultSetMetaData rsmd = rs.getMetaData();
        int colCount = rsmd.getColumnCount();
        for (int i = 1; i <= colCount; i++) {
            if ("IS_AUTOINCREMENT".equals(rsmd.getColumnName(i))) { //$NON-NLS-1$
                supportsIsAutoIncrement = true;
            }
            if ("IS_GENERATEDCOLUMN".equals(rsmd.getColumnName(i))) { //$NON-NLS-1$
                supportsIsGeneratedColumn = true;
            }
        }

        while (rs.next()) {
            IntrospectedColumn introspectedColumn = ObjectFactory
                    .createIntrospectedColumn(context);

            introspectedColumn.setTableAlias(tc.getAlias());
            introspectedColumn.setJdbcType(rs.getInt("DATA_TYPE")); //$NON-NLS-1$
            introspectedColumn.setLength(rs.getInt("COLUMN_SIZE")); //$NON-NLS-1$
            introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME")); //$NON-NLS-1$
            introspectedColumn.setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable); //$NON-NLS-1$
            introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS")); //$NON-NLS-1$
            introspectedColumn.setRemarks(rs.getString("REMARKS")); //$NON-NLS-1$
            introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF")); //$NON-NLS-1$

            if (supportsIsAutoIncrement) {
                introspectedColumn.setAutoIncrement("YES".equals(rs.getString("IS_AUTOINCREMENT"))); //$NON-NLS-1$ //$NON-NLS-2$
            }

            if (supportsIsGeneratedColumn) {
                introspectedColumn.setGeneratedColumn("YES".equals(rs.getString("IS_GENERATEDCOLUMN"))); //$NON-NLS-1$ //$NON-NLS-2$
            }

            ActualTableName atn = new ActualTableName(
                    rs.getString("TABLE_CAT"), //$NON-NLS-1$
                    rs.getString("TABLE_SCHEM"), //$NON-NLS-1$
                    rs.getString("TABLE_NAME")); //$NON-NLS-1$

            List<IntrospectedColumn> columns = answer.get(atn);
            if (columns == null) {
                columns = new ArrayList<IntrospectedColumn>();
                answer.put(atn, columns);
            }

            columns.add(introspectedColumn);


        }

        closeResultSet(rs);

        return answer;
    }

    /**
     * 实例化所有table对象
     * @param tc
     * @param columns
     * @return
     */
    private List<IntrospectedTable> calculateIntrospectedTables(
            TableConfiguration tc,
            Map<ActualTableName, List<IntrospectedColumn>> columns) {
        boolean delimitIdentifiers = tc.isDelimitIdentifiers()
                || StringUtility.stringContainsSpace(tc.getCatalog())
                || StringUtility.stringContainsSpace(tc.getSchema())
                || StringUtility.stringContainsSpace(tc.getTableName());

        List<IntrospectedTable> answer = new ArrayList<IntrospectedTable>();

        for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
                .entrySet()) {
            ActualTableName atn = entry.getKey();

            // we only use the returned catalog and schema if something was
            // actually
            // specified on the table configuration. If something was returned
            // from the DB for these fields, but nothing was specified on the
            // table
            // configuration, then some sort of DB default is being returned
            // and we don't want that in our SQL
            FullyQualifiedTable table = new FullyQualifiedTable(
                    StringUtility.stringHasValue(tc.getCatalog()) ? atn
                            .getCatalog() : null,
                    StringUtility.stringHasValue(tc.getSchema()) ? atn
                            .getSchema() : null,
                    atn.getTableName(),
                    tc.getDomainObjectName(),
                    tc.getAlias(),
                    StringUtility.isTrue(tc.getProperty(PropertyRegistry.TABLE_IGNORE_QUALIFIERS_AT_RUNTIME)),
                    tc.getProperty(PropertyRegistry.TABLE_RUNTIME_CATALOG),
                    tc.getProperty(PropertyRegistry.TABLE_RUNTIME_SCHEMA),
                    tc.getProperty(PropertyRegistry.TABLE_RUNTIME_TABLE_NAME),
                    delimitIdentifiers, context);

            IntrospectedTable introspectedTable = ObjectFactory
                    .createIntrospectedTable(tc, table, context);

            for (IntrospectedColumn introspectedColumn : entry.getValue()) {
                introspectedTable.addColumn(introspectedColumn);
            }

            calculatePrimaryKey(table, introspectedTable);

            enhanceIntrospectedTable(introspectedTable);

            answer.add(introspectedTable);
        }

        return answer;
    }
    private void calculatePrimaryKey(FullyQualifiedTable table,
                                     IntrospectedTable introspectedTable) {
        ResultSet rs = null;

//        try {
//            rs = databaseMetaData.getPrimaryKeys(
//                    table.getIntrospectedCatalog(), table
//                            .getIntrospectedSchema(), table
//                            .getIntrospectedTableName());
//        } catch (SQLException e) {
//            closeResultSet(rs);
//            return;
//        }
//
//        try {
//            // keep primary columns in key sequence order
//            Map<Short, String> keyColumns = new TreeMap<Short, String>();
//            while (rs.next()) {
//                String columnName = rs.getString("COLUMN_NAME"); //$NON-NLS-1$
//                short keySeq = rs.getShort("KEY_SEQ"); //$NON-NLS-1$
//                keyColumns.put(keySeq, columnName);
//            }
//
//            for (String columnName : keyColumns.values()) {
//                introspectedTable.addPrimaryKeyColumn(columnName);
//            }
//        } catch (SQLException e) {
//            // ignore the primary key if there's any error
//        } finally {
//            closeResultSet(rs);
//        }
    }
    private void enhanceIntrospectedTable(IntrospectedTable introspectedTable) {
//        try {
//            FullyQualifiedTable fqt = introspectedTable.getFullyQualifiedTable();

//            ResultSet rs = databaseMetaData.getTables(fqt.getIntrospectedCatalog(), fqt.getIntrospectedSchema(),
//                    fqt.getIntrospectedTableName(), null);
//            if (rs.next()) {
//                String remarks = rs.getString("REMARKS"); //$NON-NLS-1$
//                String tableType = rs.getString("TABLE_TYPE"); //$NON-NLS-1$
//                introspectedTable.setRemarks(remarks);
//                introspectedTable.setTableType(tableType);
//            }
//            closeResultSet(rs);
//        } catch (SQLException e) {
//
//        }
    }

    /**
     * 关闭ResultSet
     * @param rs
     */
    private void closeResultSet(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                // ignore
            }
        }
    }

    private void calculateExtraColumnInformation(TableConfiguration tc,
                                                 Map<ActualTableName, List<IntrospectedColumn>> columns) {
        StringBuilder sb = new StringBuilder();
        Pattern pattern = null;
        String replaceString = null;
//        if (tc.getColumnRenamingRule() != null) {
//            pattern = Pattern.compile(tc.getColumnRenamingRule()
//                    .getSearchString());
//            replaceString = tc.getColumnRenamingRule().getReplaceString();
//            replaceString = replaceString == null ? "" : replaceString; //$NON-NLS-1$
//        }

        for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
                .entrySet()) {
            for (IntrospectedColumn introspectedColumn : entry.getValue()) {
                String calculatedColumnName;
                if (pattern == null) {
                    calculatedColumnName = introspectedColumn
                            .getActualColumnName();
                } else {
                    Matcher matcher = pattern.matcher(introspectedColumn
                            .getActualColumnName());
                    calculatedColumnName = matcher.replaceAll(replaceString);
                }

                if (StringUtility.isTrue(tc
                        .getProperty(PropertyRegistry.TABLE_USE_ACTUAL_COLUMN_NAMES))) {
                    introspectedColumn.setJavaProperty(
                            getValidPropertyName(calculatedColumnName));
                } else if (StringUtility.isTrue(tc
                        .getProperty(PropertyRegistry.TABLE_USE_COMPOUND_PROPERTY_NAMES))) {
                    sb.setLength(0);
                    sb.append(calculatedColumnName);
                    sb.append('_');
                    sb.append(getCamelCaseString(
                            introspectedColumn.getRemarks(), true));
                    introspectedColumn.setJavaProperty(
                            getValidPropertyName(sb.toString()));
                } else {
                    introspectedColumn.setJavaProperty(
                            getCamelCaseString(calculatedColumnName, false));
                }

                FullyQualifiedJavaType fullyQualifiedJavaType = javaTypeResolver
                        .calculateJavaType(introspectedColumn);

                if (fullyQualifiedJavaType != null) {
                    introspectedColumn
                            .setFullyQualifiedJavaType(fullyQualifiedJavaType);
                    introspectedColumn.setJdbcTypeName(javaTypeResolver
                            .calculateJdbcTypeName(introspectedColumn));
                } else {
                    // type cannot be resolved. Check for ignored or overridden
                    boolean warn = true;
                    if (tc.isColumnIgnored(introspectedColumn
                            .getActualColumnName())) {
                        warn = false;
                    }

//                    ColumnOverride co = tc.getColumnOverride(introspectedColumn
//                            .getActualColumnName());
//                    if (co != null
//                            && stringHasValue(co.getJavaType())
//                            && stringHasValue(co.getJavaType())) {
//                        warn = false;
//                    }

                    // if the type is not supported, then we'll report a warning

                }

//                if (context.autoDelimitKeywords()
//                        && SqlReservedWords.containsWord(introspectedColumn
//                        .getActualColumnName())) {
//                    introspectedColumn.setColumnNameDelimited(true);
//                }
//
//                if (tc.isAllColumnDelimitingEnabled()) {
//                    introspectedColumn.setColumnNameDelimited(true);
//                }
            }
        }
    }
}
